package webhook

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"gitverse.ru/synapse/kubelatte/pkg/api/common"
	"gitverse.ru/synapse/kubelatte/pkg/mutation"
	"gitverse.ru/synapse/kubelatte/pkg/observability/logs"
	"gitverse.ru/synapse/kubelatte/pkg/util"
	"gitverse.ru/synapse/kubelatte/pkg/validation"
	"io"
	"k8s.io/api/admission/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"net/http"
	"sigs.k8s.io/yaml"
	"strings"
)

func (whsvr *Server) healthz(w http.ResponseWriter, _ *http.Request) {
	w.WriteHeader(http.StatusOK)
}

func (whsvr *Server) liveness(w http.ResponseWriter, _ *http.Request) {
	w.WriteHeader(http.StatusOK)
}

func (whsvr *Server) readyness(w http.ResponseWriter, _ *http.Request) {
	w.WriteHeader(http.StatusOK)
}

func (whsvr *Server) mutateHandler(w http.ResponseWriter, r *http.Request) {
	ctx := context.Background()

	var admissionResponse *v1beta1.AdmissionResponse
	var body, err = getIncomingRequestBody(ctx, w, r, "mutation")
	if body == nil {
		return
	}

	admissionReview := v1beta1.AdmissionReview{}
	_, _, err = deserializer.Decode(body, nil, &admissionReview)
	if err != nil {
		logs.Errorf("api=mutateHandler, reason=deserializer.Decode, message=cannot decode body, err=%v", err)
		http.Error(w, "empty body", http.StatusBadRequest)
		admissionResponse = &v1beta1.AdmissionResponse{
			Result: &metav1.Status{
				Message: err.Error(),
			},
		}
		createAdmissionResponse(ctx, w, &admissionReview, admissionResponse)
		return
	}

	arfields, _, err := parseRequest(ctx, admissionReview.Request, body)
	if err != nil {
		logs.Errorf("api=mutateHandler, reason=deserializer.Decode, message=cannot decode body, err=%v", err)
		http.Error(w, "empty body", http.StatusBadRequest)
		admissionResponse = &v1beta1.AdmissionResponse{
			Result: &metav1.Status{
				Message: err.Error(),
			},
		}
		createAdmissionResponse(ctx, w, &admissionReview, admissionResponse)
		return
	}

	patches := mutation.MC.ApplyMutation(ctx, arfields, admissionReview.Request.Object.Raw)
	patches = whsvr.sideEffectLauncher.StartSideEffect(ctx, arfields, patches)

	patchBytes, _ := json.Marshal(patches)
	createAdmissionResponse(ctx, w, &admissionReview, &v1beta1.AdmissionResponse{
		Allowed: true,
		Patch:   patchBytes,
		PatchType: func() *v1beta1.PatchType {
			pt := v1beta1.PatchTypeJSONPatch
			return &pt
		}(),
	})
}

func (whsvr *Server) validateHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
	if whsvr.state != ServerReady {
		w.WriteHeader(http.StatusServiceUnavailable)
		_, _ = w.Write([]byte(ServerStateText[whsvr.state]))
		return
	}
	ctx := context.Background()

	var body, err = getIncomingRequestBody(ctx, w, r, "validation")
	if body == nil {
		return
	}

	admissionResponse := &v1beta1.AdmissionResponse{
		Allowed: true,
	}

	ar := v1beta1.AdmissionReview{}
	_, _, err = deserializer.Decode(body, nil, &ar)
	if err != nil {
		logs.Errorf("api=validation, reason=deserializer.Decode, message=cannot decode body, err=%v", err)
		http.Error(w, "empty body", http.StatusBadRequest)
		admissionResponse.Allowed = false
		admissionResponse.Result = &metav1.Status{
			Message: err.Error(),
		}
	} else {
		admissionResponse.UID = ar.Request.UID

		requestData, originalRequest, err := parseRequest(ctx, ar.Request, body)
		if err != nil {
			logs.Errorf("api=validation, reason=parseRequest, message=could not parse request, err=%v", err)
			http.Error(w, fmt.Sprintf("could not parse request: %v", err), http.StatusBadRequest)
			return
		}

		err = validation.ValidateResource(ctx, originalRequest, *requestData)
		if err != nil {
			admissionResponse.Allowed = false
			admissionResponse.Result = &metav1.Status{
				Message: fmt.Sprintf("validation webhook error: %v", err),
			}
		}
	}

	ar.Response = admissionResponse
	resp, err := json.Marshal(ar)

	if err != nil {
		logs.Errorf("api=validation, reason=json.Marshal, message=could not encode response, err=%v", err)
		http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
	}

	logs.Infof("api=validation, message=ready to write response, result=%t", ar.Response.Allowed)

	if _, err := w.Write(resp); err != nil {
		logs.Errorf("api=validation, reason=w.Write, message=could not write response, err=%v", err)
		http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
	}
}

func (whsvr *Server) immutableHandler(w http.ResponseWriter, r *http.Request) {
	var body []byte

	if r.Body != nil {
		if data, err := io.ReadAll(r.Body); err == nil {
			body = data
		}
	}

	prettyS := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(string(body), "\"", "'"), " ", ""), "\n", " ")
	logs.Infof("api=immutableHandler, message=incoming request, body=%s", prettyS)

	ar := v1beta1.AdmissionReview{}
	_, _, _ = deserializer.Decode(body, nil, &ar)

	admissionResponse := &v1beta1.AdmissionResponse{
		Allowed: false,
		Result: &metav1.Status{
			Message: fmt.Sprintf("Immutable resource"),
		},
	}

	admissionResponse.UID = ar.Request.UID
	resp, _ := json.Marshal(ar)

	w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
	if _, err := w.Write(resp); err != nil {
		logs.Errorf("api=immutableHandler, reason=w.Write, message=could not write response, err=%v", err)
		http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
	}
}

func (whsvr *Server) updateStatusHandler(w http.ResponseWriter, _ *http.Request) {
	statusData, err := yaml.Marshal(util.StatusInfo)
	if err != nil {
		_, err = w.Write([]byte(err.Error()))
		if err != nil {
			logs.Errorf("api=updateStatusHandler, err=%s", err.Error())
			return
		}
		logs.Errorf("api=updateStatusHandler, err=%s", err.Error())
		return
	}
	_, err = w.Write(statusData)
	if err != nil {
		logs.Errorf("api=updateStatusHandler, err=%s", err.Error())
		return
	}
}

func getIncomingRequestBody(ctx context.Context, w http.ResponseWriter, r *http.Request, api string) ([]byte, error) {
	// verify the content type is accurate
	contentType := r.Header.Get("Content-Type")
	if contentType != "application/json" {
		logs.Errorf("api=%s, message=invalid Content-Type expect `application/json`, contentType=%s ", api, contentType)
		http.Error(w, "invalid Content-Type expect `application/json`", http.StatusUnsupportedMediaType)
		return nil, errors.New(fmt.Sprintf("invalid Content-Type %s", contentType))
	}

	var body []byte
	if r.Body != nil {
		if data, err := io.ReadAll(r.Body); err == nil {
			body = data
		}
	}

	if len(body) == 0 {
		logs.Errorf("api=%s, message=empty body received, contentType=%s, headers=%s", api, contentType, r.Header)
		http.Error(w, "empty body", http.StatusBadRequest)
		return nil, errors.New("empty body received")
	}

	prettyS := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(string(body), "\"", "'"), " ", ""), "\n", " ")
	logs.Infof("api=%s, message=incoming request, body=%s", api, prettyS)
	return body, nil
}

func createAdmissionResponse(ctx context.Context, w http.ResponseWriter, ar *v1beta1.AdmissionReview, admissionResponse *v1beta1.AdmissionResponse) {
	admissionReview := v1beta1.AdmissionReview{}

	if admissionResponse != nil {
		admissionReview.Response = admissionResponse
		if ar.Request != nil {
			admissionReview.Response.UID = ar.Request.UID
		}
	}

	resp, err := json.Marshal(admissionReview)

	if err != nil {
		logs.Errorf("reason=json.Marshal, message=could not encode response, err=%v", err)
		http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
	if _, err := w.Write(resp); err != nil {
		logs.Errorf("reason=w.Write, message=could not write response, err=%v", err)
		http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
		return
	}
	logs.Info(string(resp))
}

func parseRequest(ctx context.Context, request *v1beta1.AdmissionRequest, body []byte) (*common.ARFields, map[string]interface{}, error) {
	var obj map[string]interface{}
	if request.Operation != v1beta1.Delete && request.Object.Raw != nil {
		err := json.Unmarshal(request.Object.Raw, &obj)
		if err != nil {
			return nil, nil, err
		}
	}

	var oldObj map[string]interface{}
	if request.Operation != v1beta1.Create && request.OldObject.Raw != nil {
		err := json.Unmarshal(request.OldObject.Raw, &oldObj)
		if err != nil {
			return nil, nil, err
		}
	}
	var original map[string]interface{}
	err := json.Unmarshal(body, &original)
	if err != nil {
		return nil, nil, err
	}

	origlRequest, ok := original["request"]
	if !ok {
		logs.Debugf("there is no \"request\" field in body")
	}

	originalRequest, ok := origlRequest.(map[string]interface{})

	return &common.ARFields{
		Kind:      request.Kind,
		Namespace: request.Namespace,
		UserInfo:  common.ARUserInfo{Username: request.UserInfo.Username},
		Operation: request.Operation,
		Object:    obj,
		OldObject: oldObj,
	}, originalRequest, nil
}
